Expand description
crabgrind
is a small library that enables Rust
programs to tap into Valgrind
’s tools and virtualized environment.
Valgrind
offers a “client request interface” that is accessible through C
macros in its header files.
However, these macros can’t be used in languages fortunate enough to lack C
preprocessor support, such as Rust
. To address this,crabgrind
wraps those macros in C
functions and expose this API via FFI.
Essentially, crabgrind
acts as a thin wrapper. It adds some type conversions and structure, but all the real things are done by Valgrind
itself.
§Valgrind 3 API coverage
- Supported tool-specific client request interface: valgrind, callgrind, memcheck, helgrind, massif, cachegrind, dhat
- Monitor commands interface
§Quickstart
crabgrind
does not link against Valgrind
but instead reads its header files, which must be accessible during build.
If you have installed Valgrind
using OS-specific package manager, the paths to the headers are likely to be resolved automatically by cc
.
In case of manual installation, you can set the path to the Valgrind
headers location through the DEP_VALGRIND
environment variable. For example:
DEP_VALGRIND=/usr/include cargo build
Next, add dependency to Cargo.toml
[dependencies]
crabgrind = "0.1"
Then, use some of Valgrind’s API
use crabgrind as cg;
fn main() {
if matches!(cg::run_mode(), cg::RunMode::Native) {
println!("run me under Valgrind");
} else {
cg::println!("Hey, Valgrind!");
}
}
and run under Valgrind
cargo build
valgrind ./target/debug/appname
§Examples
§Print current function stack-trace to the Valgrind log
Valgrind provides VALGRIND_PRINTF_BACKTRACE
macro to print the message with the stack-trace attached,
crabgrind::print_stacktrace
is it’s crabbed wrapper.
use crabgrind as cg;
#[inline(never)]
fn print_trace(){
let mode = cg::run_mode();
cg::print_stacktrace!("current mode: {mode:?}");
}
print_trace();
§Exclude expensive initialization code from the measurements
One way to do this would be to turn off stats collection at stratup with the
--collect-atstart=no
callgrind command-line attribute, and enable/disable it from the code with callgrind::toggle_collect
use crabgrind as cg;
// ... some expensive initialization
cg::callgrind::toggle_collect();
// code of interest
cg::callgrind::toggle_collect();
// ... some deinitialization
§Run a closure on the real CPU while running under Valgrind
We can run on the real CPU instead of the virtual one using valgrind::non_simd_call
,
refer to valgrind.h
for details on limitations and various ways to crash.
use crabgrind as cg;
let mut state = 0;
cg::valgrind::non_simd_call(|tid| {
// uncomment following line to see "the 'impossible' happened"
// println!("tid: {tid}");
state = tid;
});
println!("tid: {state}");
§Save current memory usage snapshot to a file
We’ll use Massif
tool and the monitor command
interface to run the corresponding Massif command.
use crabgrind as cg;
let heap = String::from("alloca");
if cg::monitor_command("snapshot mem.snapshot").is_ok(){
println!("snapshot is saved to \"mem.snapshot\"");
}
§Dump Callgrind counters on a per-function basis
use crabgrind as cg;
fn factorial1(num: u128) -> u128 {
match num {
0 => 1,
1 => 1,
_ => factorial1(num - 1) * num,
}
}
fn factorial2(num: u128) -> u128 {
(1..=num).product()
}
cg::callgrind::zero_stats();
let a = factorial1(20);
cg::callgrind::dump_stats("factorial1");
let b = factorial2(20);
cg::callgrind::dump_stats("factorial2");
assert_eq!(a,b);
cg::callgrind::dump_stats(None);
§Overhead
from Valgrind docs
The code added to your binary has negligible performance impact: on x86, amd64, ppc32, ppc64 and ARM, the overhead is 6 simple integer instructions and is probably undetectable except in tight loops.
… the code does nothing when not run on Valgrind, so you are not forced to run your program under Valgrind just because you use the macros in this file.
Although your loops should be very tight (like a well-executed dance move) to notice any impact, keep in mind that:
- Wrapping each macros in a function implies function call overhead regardless of the run mode. This can potentially impact the performance of your Rust program. See linker-plugin-lto branch for a possible workaround.
- Functions that return
std::result::Result
involve branching, which can also have an impact on performance. - Functions that take strings as parameters internally convert them to
std::ffi::CString
, which can introduce additional overhead.
Modules§
Macros§
- Prints to the Valgrind’s log.
- Prints to the Valgrind’s log, with the current stacktrace attached.
- Prints to the Valgrind’s log, with a newline.
Enums§
- Current run mode
Functions§
- Change the value of a dynamic command line option.
- Returns the number of errors found so far by Valgrind
- Disable error reporting for this thread
- Re-enable error reporting for this thread
- Execute arbitrary Valgrind Monitor command
- Returns the
RunMode
app running in